﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Diagnostics;
using System.Threading;
using System.Collections.Generic;

namespace System.Windows.Media.DirectShow.UI
{
    public class BarVisualization : PeaksVisualization
    {

        private const int PeakFrequencies = 19;

        private Rectangle[] bars = new Rectangle[PeakFrequencies];
        private Line[] lines = new Line[PeakFrequencies];
        private LinearGradientBrush[] gradients = new LinearGradientBrush[PeakFrequencies];
        private Storyboard[] storyboards = new Storyboard[PeakFrequencies];
        private Storyboard[] lineStoryboards = new Storyboard[PeakFrequencies];

        private const double returnToZeroSpeed = 1000d;
        private const double goToPeakUpSpeed = 50d;
        private const double goToPeakDownSpeed = 100d;

        public static readonly DependencyProperty BarSegmentMarginProperty = DependencyProperty.Register("BarSegmentMargin", typeof(Double), typeof(BarVisualization), new PropertyMetadata(1d, OnSettingsChanged));
        public static readonly DependencyProperty BarMarginProperty = DependencyProperty.Register("BarMargin", typeof(Double), typeof(BarVisualization), new PropertyMetadata(1d, OnSettingsChanged));
        public static readonly DependencyProperty BarSegmentHeightProperty = DependencyProperty.Register("BarSegmentHeight", typeof(Double), typeof(BarVisualization), new PropertyMetadata(4d, OnSettingsChanged));
        public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register("StrokeThickness", typeof(Double), typeof(BarVisualization), new PropertyMetadata(1d, OnSettingsChanged));
        public static readonly DependencyProperty StrokeBrushProperty = DependencyProperty.Register("StrokeBrush", typeof(Brush), typeof(BarVisualization), new PropertyMetadata(new SolidColorBrush(Colors.DarkGray), OnSettingsChanged));
        public static readonly DependencyProperty BarBrushProperty;

        static BarVisualization()
        {
            // Colorazione della barra
            LinearGradientBrush fill = new LinearGradientBrush();
            fill.StartPoint = new Point(0, 0);
            fill.EndPoint = new Point(0, 1);
            fill.GradientStops.Add(new GradientStop { Offset = 0, Color = Colors.Red });
            fill.GradientStops.Add(new GradientStop { Offset = 0.5, Color = Colors.Yellow });
            fill.GradientStops.Add(new GradientStop { Offset = 1, Color = Colors.Green });

            BarBrushProperty = DependencyProperty.Register("BarBrush", typeof(Brush), typeof(BarVisualization), new PropertyMetadata(fill, OnBarBrushChanged));
        }

        public BarVisualization()
        {
            for (int x = 0; x < PeakFrequencies; x++)
            {
                Rectangle bar = new Rectangle();
                bars[x] = bar;

                // Clip per fare le tacchette del peak meter
                GeometryGroup group = new GeometryGroup();
                bar.Clip = group;
                bar.Fill = this.BarBrush;

                this.Children.Add(bar);

                // Gradiente per l'opacity mask per mostrare il peak
                LinearGradientBrush brush = new LinearGradientBrush();
                brush.StartPoint = new Point(0, 1);
                brush.EndPoint = new Point(0, 0);
                brush.GradientStops.Add(new GradientStop { Color = Colors.Black, Offset = 0 });
                gradients[x] = brush;

                Storyboard storyboard = new Storyboard();
                storyboard.FillBehavior = FillBehavior.HoldEnd;
                storyboards[x] = storyboard;
                lineStoryboards[x] = new Storyboard();

                Line line = new Line();
                this.Children.Add(line);
                lines[x] = line;

                TranslateTransform tt = new TranslateTransform();
                line.RenderTransform = tt;

                // Animazione per le linee
                DoubleAnimation danim = new DoubleAnimation();
                Storyboard.SetTargetProperty(danim, new PropertyPath("Y"));
                Storyboard.SetTarget(danim, tt);
                lineStoryboards[x].Children.Add(danim);

                // Doppia animazione per spostare il gradiente del trasparente e del colore
                for (int i = 0; i < 2; i++)
                {
                    DoubleAnimationUsingKeyFrames anim = new DoubleAnimationUsingKeyFrames();
                    anim.KeyFrames.Add(new LinearDoubleKeyFrame());
                    anim.KeyFrames.Add(new LinearDoubleKeyFrame());

                    GradientStop gs = new GradientStop { Color = (i != 0) ? Colors.Transparent : Colors.Black, Offset = 0 };
                    brush.GradientStops.Add(gs);
                    bar.OpacityMask = brush;

                    storyboard.Children.Add(anim);

                    Storyboard.SetTargetProperty(anim, new PropertyPath("Offset"));
                    Storyboard.SetTarget(anim, gs);
                }
            }
        }

        public Double StrokeThickness
        {
            get { return (Double)this.GetValue(StrokeThicknessProperty); }
            set { this.SetValue(StrokeThicknessProperty, value); }
        }

        public Brush StrokeBrush
        {
            get { return (Brush)this.GetValue(StrokeBrushProperty); }
            set { this.SetValue(StrokeBrushProperty, value); }
        }

        public Brush BarBrush
        {
            get { return (Brush)this.GetValue(BarBrushProperty); }
            set { this.SetValue(BarBrushProperty, value); }
        }

        public Double BarSegmentMargin
        {
            get { return (Double)this.GetValue(BarSegmentMarginProperty); }
            set { this.SetValue(BarSegmentMarginProperty, value); }
        }

        public Double BarMargin
        {
            get { return (Double)this.GetValue(BarMarginProperty); }
            set { this.SetValue(BarMarginProperty, value); }
        }

        public Double BarSegmentHeight
        {
            get { return (Double)this.GetValue(BarSegmentHeightProperty); }
            set { this.SetValue(BarSegmentHeightProperty, value); }
        }

        private static void OnBarBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            BarVisualization am = (BarVisualization)d;
            foreach (Rectangle b in am.bars)
                b.Fill = am.BarBrush;
        }

        private static void OnSettingsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            BarVisualization am = (BarVisualization)d;
            am.InvalidateArrange();
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            return new Size(Math.Min((Double.IsNaN(this.Width)) ? availableSize.Width : this.Width, availableSize.Width),
                Math.Min((Double.IsNaN(this.Height)) ? availableSize.Height : this.Height, availableSize.Height));
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            double interval = (finalSize.Width / PeakFrequencies) - BarMargin;

            for (int x = 0; x < PeakFrequencies; x++)
            {
                // Per ogni picco preparo le tacchette
                GeometryGroup group = bars[x].Clip as GeometryGroup;
                group.Children.Clear();
                double rows = finalSize.Height / (BarSegmentHeight + BarSegmentMargin);
                for (int r = 0; r < rows; r++)
                {
                    group.Children.Add(new RectangleGeometry { Rect = new Rect(0, r * (BarSegmentHeight + BarSegmentMargin), interval, BarSegmentHeight) });
                }

                // Dispongo ogni barra in modo allineato
                Rect rect = new Rect(x * (interval + BarMargin), 0, interval, finalSize.Height);
                bars[x].Arrange(rect);

                Line line = lines[x];
                line.X1 = rect.X;
                line.X2 = rect.Right;
                line.StrokeThickness = this.StrokeThickness;
                line.Stroke = this.StrokeBrush;
                ((TranslateTransform)lines[x].RenderTransform).Y = finalSize.Height;
            }

            return finalSize;
        }

        private TimeSpan start;

        public override void Run(TimeSpan start)
        {
            base.Run(start);

            this.start = start;
        }

        protected override void RenderPeakValues(byte[] peaks, IMediaSample sample, IReferenceClock clock)
        {
            clock.AdviseTime(sample.StartTime + start, delegate(object s)
            {
                this.SetPeakValues((byte[])s);
            },
            peaks);
        }

        private void SetPeakValues(byte[] peaks)
        {
            for (int x = 0; x < PeakFrequencies; x++)
            {
                // Peak nuovo
                double p = Math.Min(1d, peaks[x] / 100d);
                // Posizione attuale del peak
                double cp = gradients[x].GradientStops[1].Offset;
                // Posizione attuale della linea
                double lp = ((TranslateTransform)lines[x].RenderTransform).Y;

                double lineFrom = this.ActualHeight - (this.ActualHeight * p);
                if (lp > lineFrom)
                {
                    DoubleAnimation danim = (DoubleAnimation)lineStoryboards[x].Children[0];
                    danim.To = this.ActualHeight;
                    danim.From = lineFrom;
                    danim.Duration = new Duration(TimeSpan.FromMilliseconds(returnToZeroSpeed * 4 * p));

                    lineStoryboards[x].Begin();
                }

                for (int c = 0; c < storyboards[x].Children.Count; c++)
                {
                    DoubleAnimationUsingKeyFrames anim = (DoubleAnimationUsingKeyFrames)storyboards[x].Children[c];
                    double diff = (cp - p);
                    if (diff < 0)
                        diff = diff * -1;
                    // Primo frame: dal valore attuale a quello nuovo - veloce
                    anim.KeyFrames[0].KeyTime = TimeSpan.FromMilliseconds((p < cp) ? goToPeakDownSpeed : goToPeakUpSpeed * diff);
                    anim.KeyFrames[0].Value = p;
                    // Secondo frame: dal valore nuovo a zero - lento
                    anim.KeyFrames[1].Value = 0;
                    anim.KeyFrames[1].KeyTime = anim.KeyFrames[0].KeyTime.TimeSpan + TimeSpan.FromMilliseconds(returnToZeroSpeed * p);
                }

                // Inizio l'animazione
                storyboards[x].Begin();
            }
        }
    }
}
